Using textures

In this tutorial we will see how textures can be used during rendering. This will allow us to spicen up a bit the rendering of our sphere.

For this, we will need to load the texture within the component, and edit the program and shader to actually take it into account. Let's work our way through it !

Working with textures

The component uses the Texture class for this purpose. First, include :

#include <NilkinsGraphics/Textures/Texture.h> #include <NilkinsGraphics/Textures/TextureManager.h>

Now that we have everything we need, we can use them, preferrably before finalizing the Shader :

nkGraphics::Texture* tex = nkGraphics::TextureManager::getInstance()->createOrRetrieve("tex") ; tex->setResourcePath("cloudMap.dds") ; tex->load() ;

We first create the texture, from the dedicated manager. Once that is done, we only need to specify the texture file to use, and load our resource.
For supported formats, please see within the API documentation.

Last missing part : the sampler

To use the texture within the shader, we will need a sampler. The sampler defines the way the texture data is read. In the component, we will need the Sampler class.

#include <NilkinsGraphics/Samplers/Sampler.h> #include <NilkinsGraphics/Samplers/SamplerManager.h>

Allowing us to write our code :

nkGraphics::Sampler* sampler = nkGraphics::SamplerManager::getInstance()->createOrRetrieve("sampler") ; sampler->load() ;

As simple as that : we request the sampler, and will load it right away as the default settings for it will fit our needs.

Modifying the program

Of course, being able to feed a texture and a sampler to a program means it needs to interpret the data. For this, we will change a bit the sources we wrote :

sources.setVertexMemory ( R"eos( cbuffer PassBuffer : register(b0) { matrix view ; matrix proj ; } struct VertexInput { float4 position : POSITION ; float2 uvs : TEXCOORD0 ; matrix world : WORLDMAT ; } ; struct PixelInput { float4 position : SV_POSITION ; float2 uvs : TEXCOORD0 ; } ; PixelInput main (VertexInput input) { PixelInput result ; matrix mvp = mul(input.world, mul(view, proj)) ; result.position = mul(input.position, mvp) ; result.uvs = input.uvs ; return result ; } )eos" ) ; sources.setPixelMemory ( R"eos( struct PixelInput { float4 position : SV_POSITION ; float2 uvs : TEXCOORD0 ; } ; Texture2D tex : register(t0) ; SamplerState customSampler : register(s0) ; float4 main (PixelInput input) : SV_TARGET { return tex.Sample(customSampler, input.uvs) ; } )eos" ) ;

Let's take a look at the vertex shader first. Amongst the difference, we have the introduction, within the VertexInput, of the uvs input. This input is given by the mesh itself (our sphere features texture coordinates). This will be passed to the pixel stage, so we also add it to the PixelInput structure, and populate its value within the main method.

The pixel stage, on its side, receives the uvs, but also introduces the need for a Texture2D on slot 0, along with a SamplerState, also on slot 0. The main method simply samples the texture, using the sampler and new input we defined.

Updating the shader

New input required means altering the shader to also give the data we need :

shader->addTexture(tex, 0) ; shader->addSampler(sampler, 0) ;

This maps quite directly with the HLSL : we set the texture and sampler to use on their respective register slot 0.
Note that both classes are totally independant. It is possible to use a texture with many samplers, and many samplers on one texture, depending on your needs for the rendering.

And with all of that, our program is ready to be compiled and run again.

Cloudy sphere
It's getting cloudy !

In a nutshell, using a texture goes through :

  1. Creating the texture, specifying its input and load it
  2. Create a sampler if required, and prepare its parameter if needed
  3. Update the shader to feed it to the program needing them

With this, the pipeline to use a texture is covered ! Next step, we will work a bit on the rendering pipeline currently set to make it fancier.